吐血总结:MySQL性能如何优化?
前言
上篇讲了MySQL的索引优化,此篇文章从大范围讲一下MySQL数据库到底该如何优化?这个问题在面试中时常被问到,今天陈某来总结下。
SQL 优化的几个步骤
1. 通过show status
命令了解各种 SQL 的执行效率
show [session | global] status;
可以根据需要加上参数来显示 session
级(当前连接,默认)和global
级(自数据库上次启动至今)的统计结果。
show status like 'Com_%'; ---显示当前连接所有统计参数的值。
Com_xxx
表示每个xxx
语句执行的次数,通常需要注意的是下面几个参数:Com_select/Com_insert/Com_update/Com_delete
。
2. 定位执行效率较低的 SQL 语句
通过
show processlist
命令实时查看当前 SQL 的执行情况;通过
慢查询日志
定位出现的问题。
3. 通过explain
或 desc
分析低效 SQL 的执行计划
可以参考上篇文章Mysql 探索之 Explain 执行计划详解
4. 通过show profile
分析 SQL。
show profile
能帮我们了解时间都耗费到哪里去了。通过
show profiles
我们能够更清楚了解 SQL 执行的过程;
5. 通过trace
分析优化器如何选择执行计划
MySQL5.6
提供了对 SQL 的跟踪trace
,能帮我们了解为什么优化器选择执行 A 计划而不是 B 计划,进一步理解优化器的行为。
6. 确定问题并采取相应的优化措施。
MySQL 常用的 SQL 语句优化方法
应尽量避免在
where
子句中使用!=
或<>
操作符,否则将引擎放弃使用索引而进行全表扫描。对查询进行优化,应尽量避免全表扫描,首先应考虑在
where
及order by
涉及的列上建立索引。应尽量避免在
where
子句中对字段进行null
值判断,否则将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num is null
可以在 num 上设置默认值 0,确保表中 num 列没有 null 值,然后这样查询:
select id from t where num=0
避免在
where
子句中使用or
来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描。前导模糊查询将导致全表扫描
select id from t where name like ‘%c%’
下面使用索引
select id from t where name like ‘c%’
not in
也要慎用,否则会导致全表扫描;对于连续的数值,能用between
就不要用in
了,尽量使用exists
代替in
。如果在
where
子句中使用参数,也会导致全表扫描。因为 SQL 只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:
select id from t where num=@num
可以改为强制查询使用索引:
select id from t with(index(索引名)) where num=@num
应尽量避免在 where
子句中对字段进行表达式与函数或其他表达式运算操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2=100
,应改为:select id from t where num=100*2
select id from t where substring(name,1,3)='abc'
;name
以abc
开头的id
,应改为:select id from t where name like 'abc%'
select id from t where datediff(day,createdate,'2005-11-30')=0 –'2005-11-30′
生成的 id,应改为:select id from t where createdate>=’2005-11-30′ and createdate<'2005-12-01'
Update
语句,如果只更改 1、2 个字段,不要 Update 全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志。在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
并不是所有索引对查询都有效,SQL 是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL 查询可能不会去利用索引。如一表中有字段 sex,male、female 几乎各一半,那么即使在 sex 上建了索引也对查询效率起不了作用。
索引并不是越多越好,索引固然可以提高相应的
select
的效率,但同时也降低了insert
及update
的效率,因为insert
或update
时有可能会重建索引。一个表的索引数较好不要超过 6 个。应尽可能的避免更新
clustered
索引数据列,因为clustered
索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
任何地方都不要使用
select * from t
,用具体的字段列表代替*
,不要返回用不到的任何字段。对于多张大数据量(这里几百条就算大了)的表
JOIN
,要先分页再JOIN
,否则逻辑读会很高,性能很差。尽量使用表变量来代替临时表。
考虑使用
临时表
暂存中间结果。临时表并不是不可使用,适当地使用它们可以使某些查询更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。将临时结果暂存在临时表,后面的查询就在tempdb
中查询了,这可以避免程序中多次扫描主表,也大大减少了程序执行中共享锁
阻塞更新锁
,减少了阻塞,提高了并发性能。但是,对于一次性事件,较好使用导出表。在新建临时表时,如果一次性插入数据量很大,那么可以使用
select into
代替create table
,避免造成大量log
,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table
,然后insert
。如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先
truncate table
,然后drop table
,这样可以避免系统表的较长时间锁定。避免频繁创建和删除临时表,以减少系统表资源的消耗。
尽量避免使用游标,因为游标的效率较差。与临时表一样,游标并不是不可使用。对小型数据集使用
FAST_FORWARD
游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。在所有的存储过程和触发器的开始处设置
SET NOCOUNT ON
,在结束时设置SET NOCOUNT OFF
。尽量避免向客户端返回大数据量。
尽量避免大事务操作,提高系统并发能力。
用
where
子句替换Having
子句
避免使用 having 子句,having 只会在检索出所有记录之后才会对结果集进行过滤,这个处理需要排序,如果能通过 where 子句限制记录的数目,就可以减少这方面的开销。on、where、having 这三个都可以加条件的子句,on 是最先执行,where 次之,having 最后。
使用 Truncate 替代 delete
当需要删除全表的记录时使用
Truncate
替代delete
。在通常情况下, 回滚段(rollback segments
) 用来存放可以被恢复的信息. 如果你没有COMMIT
事务,ORACLE 会将数据恢复到删除之前的状态(准确地说是恢复到执行删除命令之前的状况) 而当运用 TRUNCATE 时, 回滚段不再存放任何可被恢复的信息.当命令运行后,数据不能被恢复.因此很少的资源被调用,执行时间也会很短。
使用表的别名:
当在 SQL 语句中连接多个表时, 请使用表的别名并把别名前缀于每个 Column 上.这样一来,就可以减少解析的时间并减少那些由 Column 歧义引起的语法错误。
使用 union all
替换union
当 SQL 语句需要
union
两个查询结果集合时,这两个结果集合会以 union all 的方式被合并,然后再输出最终结果前进行排序。如果用 union all 替代料 union,这样排序就不是不要了,效率就会因此得到提高. 需要注意的是,UNION ALL 将重复输出两个结果集合中相同记录。
用 where 替代 order by: ORDER BY
子句只在两种严格的条件下使用索引:①ORDER BY
中所有的列必须包含在相同的索引中并保持在索引中的排列顺序;②ORDER BY
中所有的列必须定义为非空;
低效: (索引不被使用)
SELECT DEPT_CODE FROM DEPT ORDER BY DEPT_TYPE
高效: (使用索引)
SELECT DEPT_CODE FROM DEPT WHERE DEPT_TYPE > 0
避免索引列的类型转换:
假设 EMP_TYPE 是一个字符类型的索引列.
SELECT … FROM EMP WHERE EMP_TYPE = 123
这个语句被转换为:SELECT … FROM EMP WHERE EMP_TYPE='123'
; 因为内部发生的类型转换, 这个索引将不会被用到! 为了避免 ORACLE 对你的 SQL 进行隐式的类型转换, 最好把类型转换用显式表现出来. 注意当字符和数值比较时, ORACLE 会优先转换数值类型到字符类型。
优化 Group by
提高
GROUP BY
语句的效率, 可以通过将不需要的记录在GROUP BY
之前过滤掉。下面两个查询返回相同结果但第二个明显就快了许多。
低效:
SELECT JOB , AVG(SAL) FROM EMP GROUP by JOB HAVING JOB = ‘PRESIDENT' OR JOB = ‘MANAGER'
高效:
SELECT JOB , AVG(SAL) FROM EMP WHERE JOB = ‘PRESIDENT' OR JOB = ‘MANAGER' GROUP by JOB
避免使用耗费资源的操作:
带有
DISTINCT
,UNION,MINUS
,INTERSECT
,ORDER BY
的 SQL 语句会启动 SQL 引擎执行耗费资源的排序(SORT)功能.DISTINCT
需要一次排序操作, 而其他的至少需要执行两次排序. 通常, 带有UNION
,MINUS
,INTERSECT
的 SQL 语句都可以用其他方式重写. 如果你的数据库的 SORT_AREA_SIZE 调配得好, 使用 UNION , MINUS, INTERSECT 也是可以考虑的, 毕竟它们的可读性很强。
在运行代码中,尽量使用 PreparedStatement
来查询,不要用Statement
。
MySQL 常用的索引优化方法
关于索引的优化,前面的文章已经详细的讲了二十条铁则,感兴趣的可以看一文带你搞懂索引如何优化!
MySQL 数据库的优化目标、常见误区和基本原则
优化目标
MySQL 数据库是常见的两个瓶颈是 CPU 和 I/O 的瓶颈,CPU 在饱和的时候一般发生在数据装入内存或从磁盘上读取数据时候。磁盘 I/O 瓶颈发生在装入数据远大于内存容量的时候。
减少 I/O 次数:I/O 永远是数据库最容易瓶颈的地方,这是由数据库的职责所决定的,大部分数据库操作中超过 90%的时间都是 IO 操作所占用的,减少 IO 次数是 SQL 优化中需要第一优先考虑,当然,也是收效最明显的优化手段。
降低 CPU 计算:除了 IO 瓶颈之外,SQL 优化中需要考虑的就是 CPU 运算量的优化了。
order by
,group by
,distinct
… 都是消耗 CPU 的大户(这些操作基本上都是 CPU 处理内存中的数据比较运算)。当我们的 IO 优化做到一定阶段之后,降低 CPU 计算也就成为了我们 SQL 优化的重要目标。
常见误区
count(1)
和count(primary_key)
优于count(*)
:
很多人为了统计记录条数,就使用
count(1)
和count(primary_key)
而不是count(*)
,他们认为这样性能更好,其实这是一个误区。对于有些场景,这样做可能性能会更差,应为数据库对count(*)
计数操作做了一些特别的优化。如在 MyISAM 引擎中,会对表的总行数进行记录,使用count(*)
可以直接取出该值。
count(column)
和count(*)
是一样的
实际上,
count(column)
和count(*)
是一个完全不一样的操作,所代表的意义也完全不一样。count(column)
是表示结果集中有多少个column
字段不为空的记录,只处理非空值。count(*)
是表示整个结果集有多少条记录,不会跳过null
值。
select a,b from …
比select a,b,c from …
可以让数据库访问更少的数据量实际上,大多数关系型数据库都是按照行(row)的方式存储,而数据存取操作都是以一个固定大小的 IO 单元(被称作 block 或者 page)为单位,一般为 4KB,8KB… 大多数时候,每个 IO 单元中存储了多行,每行都是存储了该行的所有字段(lob 等特殊类型字段除外)。所以,我们是取一个字段还是多个字段,实际上数据库在表中需要访问的数据量其实是一样的。
当然,也有例外情况,那就是我们的这个查询在索引中就可以完成,也就是说当只取 a,b 两个字段的时候,不需要回表,而 c 这个字段不在使用的索引中,需要回表取得其数据。在这样的情况下,二者的 IO 量会有较大差异。
order by
一定需要排序操作我们知道索引数据实际上是有序的,如果我们的需要的数据和某个索引的顺序一致,而且我们的查询又通过这个索引来执行,那么数据库一般会省略排序操作,而直接将数据返回,因为数据库知道数据已经满足我们的排序需求了。实际上,利用索引来优化有排序需求的 SQL,是一个非常重要的优化手段。
执行计划中有
filesort
就会进行磁盘文件排序有这个误区其实并不能怪我们,而是因为 MySQL 开发者在用词方面的问题。
filesort
是我们在使用explain
命令查看一条 SQL 的执行计划的时候可能会看到在 “Extra” 一列显示的信息。实际上,只要一条 SQL 语句需要进行排序操作,都会显示Using filesort
,这并不表示就会有文件排序操作。
基本原则
尽量少 join
MySQL 的优势在于简单,但这在某些方面其实也是其劣势。MySQL 优化器效率高,但是由于其统计信息的量有限,优化器工作过程出现偏差的可能性也就更多。对于复杂的多表 Join,一方面由于其优化器受限,再者在 Join 这方面所下的功夫还不够,所以性能表现离 Oracle 等关系型数据库前辈还是有一定距离。但如果是简单的单表查询,这一差距就会极小甚至在有些场景下要优于这些数据库前辈。
尽量少排序
排序操作会消耗较多的 CPU 资源,所以减少排序可以在缓存命中率高等 IO 能力足够的场景下会较大影响 SQL 的响应时间。对于 MySQL 来说,减少排序有多种办法,比如:上面误区中提到的通过利用索引来排序的方式进行优化;减少参与排序的记录条数;非必要不对数据进行排序。
尽量避免
select *
很多人看到这一点后觉得比较难理解,上面不是在误区中刚刚说 select 子句中字段的多少并不会影响到读取的数据吗?是的,大多数时候并不会影响到 IO 量,但是当我们还存在 order by 操作的时候,select 子句中的字段多少会在很大程度上影响到我们的排序效率,此外,上面误区中不是也说了,只是大多数时候是不会影响到 IO 量,当我们的查询结果仅仅只需要在索引中就能找到的时候,还是会极大减少 IO 量的。
尽量用
join
代替子查询虽然 Join 性能并不佳,但是和 MySQL 的子查询比起来还是有非常大的性能优势。
尽量少
or
当 where 子句中存在多个条件以“或”并存的时候,MySQL 的优化器并没有很好的解决其执行计划优化问题,再加上 MySQL 特有的 SQL 与 Storage 分层架构方式,造成了其性能比较低下,很多时候使用
union all
或者是union
(必要的时候)的方式来代替or
会得到更好的效果。尽量用
union all
代替union
union 和 union all 的差异主要是前者需要将两个(或者多个)结果集合并后再进行唯一性过滤操作,这就会涉及到排序,增加大量的 CPU 运算,加大资源消耗及延迟。所以当我们可以确认不可能出现重复结果集或者不在乎重复结果集的时候,尽量使用 union all 而不是 union。
尽量早过滤
这一优化策略其实最常见于索引的优化设计中(将过滤性更好的字段放得更靠前)。在 SQL 编写中同样可以使用这一原则来优化一些 Join 的 SQL。比如我们在多个表进行分页数据查询的时候,我们最好是能够在一个表上先过滤好数据分好页,然后再用分好页的结果集与另外的表 Join,这样可以尽可能多的减少不必要的 IO 操作,大大节省 IO 操作所消耗的时间。
避免类型转换
这里所说的“类型转换”是指
where
子句中出现column
字段的类型和传入的参数类型不一致的时候发生的类型转换优先优化高并发的 SQL,而不是执行频率低某些“大”SQL
对于破坏性来说,高并发的 SQL 总是会比低频率的来得大,因为高并发的 SQL 一旦出现问题,甚至不会给我们任何喘息的机会就会将系统压跨。而对于一些虽然需要消耗大量 IO 而且响应很慢的 SQL,由于频率低,即使遇到,最多就是让整个系统响应慢一点,但至少可能撑一会儿,让我们有缓冲的机会。
从全局出发优化,而不是片面调整
SQL 优化不能是单独针对某一个进行,而应充分考虑系统中所有的 SQL,尤其是在通过调整索引优化 SQL 的执行计划的时候,千万不能顾此失彼,因小失大。
尽可能对每一条运行在数据库中的 SQL 进行 explain
优化 SQL,需要做到心中有数,知道 SQL 的执行计划才能判断是否有优化余地,才能判断是否存在执行计划问题。在对数据库中运行的 SQL 进行了一段时间的优化之后,很明显的问题 SQL 可能已经很少了,大多都需要去发掘,这时候就需要进行大量的 explain 操作收集执行计划,并判断是否需要进行优化。
MySQL 数据库的表结构优化
由于 MySQL 数据库是基于行(Row)存储的数据库,而数据库操作 IO 的时候是以 page(block)的方式,也就是说,如果我们每条记录所占用的空间量减小,就会使每个 page 中可存放的数据行数增大,那么每次 IO 可访问的行数也就增多了。反过来说,处理相同行数的数据,需要访问的 page 就会减少,也就是 IO 操作次数降低,直接提升性能。此外,由于我们的内存是有限的,增加每个 page 中存放的数据行数,就等于增加每个内存块的缓存数据量,同时还会提升内存换中数据命中的几率,也就是缓存命中率。
数据类型选择
数据库操作中最为耗时的操作就是 IO 处理,大部分数据库操作 90% 以上的时间都花在了 IO 读写上面。所以尽可能减少 IO 读写量,可以在很大程度上提高数据库操作的性能。我们无法改变数据库中需要存储的数据,但是我们可以在这些数据的存储方式方面花一些心思。下面的这些关于字段类型的优化建议主要适用于记录条数较多,数据量较大的场景,因为精细化的数据类型设置可能带来维护成本的提高,过度优化也可能会带来其他的问题:
数字类型
:非万不得已不要使用DOUBLE
,不仅仅只是存储长度的问题,同时还会存在精确性的问题。同样,固定精度的小数,也不建议使用DECIMAL
,建议乘以固定倍数转换成整数存储,可以大大节省存储空间,且不会带来任何附加维护成本。对于整数的存储,在数据量较大的情况下,建议区分开TINYINT / INT / BIGINT
的选择,因为三者所占用的存储空间也有很大的差别,能确定不会使用负数的字段,建议添加 unsigned 定义。当然,如果数据量较小的数据库,也可以不用严格区分三个整数类型。
int
类型只增主键字段=>4 字节=>每个字节 8 位=>32 位,在 CPU 加载一条指令的时候,4 字节是和 CPU 寄存器的运算有关,如:64 位,由于之前的系统一般都是 32 位的,所以在运算 4 字节的数据是刚好的,效率最高,而现今我们系统基本都是 64 位的时候,其实没有更好的利用好 CPU 运算,所以在设计表字段建议,使用 8 字节的主键bigint
,而不是直接使用 int 来做主键。
字符类型
:非万不得已不要使用 TEXT 数据类型,其处理方式决定了他的性能要低于 char 或者是 varchar 类型的处理。定长字段,建议使用 CHAR 类型,不定长字段尽量使用 VARCHAR,且仅仅设定适当的最大长度,而不是非常随意的给一个很大的最大长度限定,因为不同的长度范围,MySQL 也会有不一样的存储处理。`char(10)`` 不管该字段是否存储数据,都占 10 个字符的存储空间,char(10) 同时存在一个坑,就是存储 abc 数据后改数据库字段的值为“abc 7 个空格 ”,在精准查询(where)就必须带上后面的 7 个空格。varchar 不存的时候不占空间,存多长数据就占多少空间。
时间类型:尽量使用
TIMESTAMP
类型,因为其存储空间只需要DATETIME
类型的一半。对于只需要精确到某一天的数据类型,建议使用 DATE 类型,因为他的存储空间只需要 3 个字节,比 TIMESTAMP 还少。不建议通过 INT 类型类存储一个 unix timestamp 的值,因为这太不直观,会给维护带来不必要的麻烦,同时还不会带来任何好处。
ENUM & SET
:对于状态字段,可以尝试使用 ENUM 来存放,因为可以极大的降低存储空间,而且即使需要增加新的类型,只要增加于末尾,修改结构也不需要重建表数据。如果是存放可预先定义的属性数据呢?可以尝试使用 SET 类型,即使存在多种属性,同样可以游刃有余,同时还可以节省不小的存储空间。
LOB
类型:强烈反对在数据库中存放 LOB 类型数据,虽然数据库提供了这样的功能,但这不是他所擅长的,我们更应该让合适的工具做他擅长的事情,才能将其发挥到极致。在数据库中存储 LOB 数据就像让一个多年前在学校学过一点 Java 的营销专业人员来写 Java 代码一样。
字符编码
:字符集直接决定了数据在 MySQL 中的存储编码方式,由于同样的内容使用不同字符集表示所占用的空间大小会有较大的差异,所以通过使用合适的字符集,可以帮助我们尽可能减少数据量,进而减少 IO 操作次数。① 纯拉丁字符能表示的内容,没必要选择 latin1 之外的其他字符编码,因为这会节省大量的存储空间;② 如果我们可以确定不需要存放多种语言,就没必要非得使用 UTF8 或者其他 UNICODE 字符类型,这回造成大量的存储空间浪费;③MySQL 的数据类型可以精确到字段,所以当我们需要大型数据库中存放多字节数据的时候,可以通过对不同表不同字段使用不同的数据类型来较大程度减小数据存储量,进而降低 IO 操作次数并提高缓存命中率。
适当拆分
:有些时候,我们可能会希望将一个完整的对象对应于一张数据库表,这对于应用程序开发来说是很有好的,但是有些时候可能会在性能上带来较大的问题。当我们的表中存在类似于 TEXT 或者是很大的 VARCHAR 类型的大字段的时候,如果我们大部分访问这张表的时候都不需要这个字段,我们就该义无反顾的将其拆分到另外的独立表中,以减少常用数据所占用的存储空间。这样做的一个明显好处就是每个数据块中可以存储的数据条数可以大大增加,既减少物理 IO 次数,也能大大提高内存中的缓存命中率。
上面几点的优化都是为了减少每条记录的存储空间大小,让每个数据库中能够存储更多的记录条数,以达到减少 IO 操作次数,提高缓存命中率。下面这个优化建议可能很多开发人员都会觉得不太理解,因为这是典型的反范式设计,而且也和上面的几点优化建议的目标相违背。
适度冗余
:为什么我们要冗余?这不是增加了每条数据的大小,减少了每个数据块可存放记录条数吗?确实,这样做是会增大每条记录的大小,降低每条记录中可存放数据的条数,但是在有些场景下我们仍然还是不得不这样做:① 被频繁引用且只能通过 Join 2 张(或者更多)大表的方式才能得到的独立小字段:这样的场景由于每次 Join 仅仅只是为了取得某个小字段的值,Join 到的记录又大,会造成大量不必要的 IO,完全可以通过空间换取时间的方式来优化。不过,冗余的同时需要确保数据的一致性不会遭到破坏,确保更新的同时冗余字段也被更新。
尽量使用 NOT NULL
:NULL 类型比较特殊,SQL 难优化。虽然 MySQL NULL 类型和 Oracle 的 NULL 有差异,会进入索引中,但如果是一个组合索引,那么这个 NULL 类型的字段会极大影响整个索引的效率。很多人觉得 NULL 会节省一些空间,所以尽量让 NULL 来达到节省 IO 的目的,但是大部分时候这会适得其反,虽然空间上可能确实有一定节省,倒是带来了很多其他的优化问题,不但没有将 IO 量省下来,反而加大了 SQL 的 IO 量。所以尽量确保 DEFAULT 值不是 NULL,也是一个很好的表结构设计优化习惯。
MySQL 数据库的缓存参数优化
用处不大,忽略
总结
数据库最常用的优化方式有:SQL 语句和索引、数据库表结构、系统配置、硬件。 优化效果:SQL 语句和索引 > 数据库表结构 > 系统配置 > 硬件,但成本从低到高。 数据库的优化方法小结:
设计符合范式的数据库 选择合适的存储引擎 SQL 语句优化 索引优化:高分离字段建立索引 SQL 表结构、字段优化 数据库参数优化:IO 参数、CPU 参数 分库分表:垂直切分与水平切分 分区:将表的数据按照特定的规则放在不同的分区,提高磁盘的 IO 效率,提高数据库的性能 主从复制与读写分离:三个主要线程与 bin-log 文件、relay_log 文件,主数据库负责写操作,从数据库负责读操作 负载均衡 数据库集群 硬件
往期推荐
一文带你你搞懂索引如何优化!!!
面试官:索引是什么,如何实现?懵逼~
Mysql探索之Explain执行计划详解
Navicat Premium 12免费安装,你懂得~
IDEA插件之Mybatis Log plugin破解,亲测可用!!!